#pragma once

#include "../Comm/comm.hpp"
#include "../Comm/util.hpp"
#include "../Comm/log.hpp"

namespace ns_runner
{
    using namespace ns_util;
    using namespace ns_log;

    class runner
    {
    public:
        runner(){};
        ~runner(){};

    public:
        static void SetProcLimit(int _cpu_limit, int _mem_limit)
        {
            // CPU limit
            struct rlimit _cpu_rlimit;
            _cpu_rlimit.rlim_max = RLIM_INFINITY;
            _cpu_rlimit.rlim_cur = _cpu_limit;
            setrlimit(RLIMIT_CPU, &_cpu_rlimit);

            // Mem limit
            struct rlimit _mem_rlimit;
            _mem_rlimit.rlim_max = RLIM_INFINITY;
            _mem_rlimit.rlim_cur = _mem_limit * 1024;
            setrlimit(RLIMIT_AS, &_mem_rlimit);
        }

        /*
         * Run函数返回值:
         * < 0 : 内部错误
         * = 0 : 正常执行完毕
         * > 0 : 执行出现异常, 信号编号为返回值
         *
         * cpu_limit: 运行时可以使用最大的CPU资源上限(s)
         * mem_limit: 运行时可以使用最大的内存资源上限(KB)
         */
        static int Run(const std::string &code_name, int cpu_limit, int mem_limit)
        {
            /*----------------------------------------------------------------------------
             * 1. 正常运行代码结束, 结果正确
             * 2. 正常运行代码结束, 结果错误
             * 3. 代码异常
             * 运行结果是否正确不是Run函数考虑的,应该是运行结果与测试用例相比较来确定运行正确与否
             * Run只需要考虑代码是否正常运行完毕
             *
             * 将运行的输入输出和错误分别保存在_stdin _stdout _stderr文件中
             * _stdin:  文件运行时输入信息 ———— 不处理
             * _stdout: 文件运行时输出结果
             * _stderr: 文件运行时错误信息
             ---------------------------------------------------------------------------*/
            std::string _execute = PathUtil::EXE(code_name);
            std::string _stdin = PathUtil::IN(code_name);
            std::string _stdout = PathUtil::OUT(code_name);
            std::string _stderr = PathUtil::ERROR(code_name);

            // 打开文件
            umask(0);
            int _stdin_fd = open(_stdin.c_str(), O_CREAT | O_RDONLY, 0644);
            int _stdout_fd = open(_stdout.c_str(), O_CREAT | O_WRONLY, 0644);
            int _stderr_fd = open(_stderr.c_str(), O_CREAT | O_WRONLY, 0644);
            if (_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0)
            {
                LOG(ERROR) << "运行时打开文件出错" << std::endl;
                return -1;
            }

            pid_t pid = fork();
            if (pid < 0)
            {
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
                LOG(ERROR) << "运行时创建子进程出错" << std::endl;
                return -2;
            }
            else if (pid == 0)
            {
                /*--child--*/
                // 重定向文件
                dup2(_stdin_fd, 0);
                dup2(_stdout_fd, 1);
                dup2(_stderr_fd, 2);

                // 设置程序使用资源
                SetProcLimit(cpu_limit, mem_limit);

                // 程序替换 —— 运行可执行程序
                // 第一个参数：可执行程序的路径+文件名
                // 第二个参数：如何执行./temp/file_name.exe(_execute.c_str())
                execl(_execute.c_str(), _execute.c_str(), nullptr);
                LOG(ERROR) << "运行时执行程序替换出错" << std::endl;
                exit(1);
            }
            else
            {
                /*--parents--*/
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
                int status = 0;
                waitpid(pid, &status, 0);
                LOG(INFO) << "运行完毕, Status= " << (status & 0x7F) << std::endl;
                return status & 0x7F;
            }
        }
    };

} // namespace ns_runner
